using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.IO;
using Musicman.Entities;
using System.Threading;
using System.Collections.Specialized;
using Musicman.Common;
using System.Runtime.InteropServices;
using Musicman.Business;
using System.Diagnostics;
using System.Collections;
using Musicman.Entities.BusinessEntities;

namespace Musicman.Utilities
{
    public delegate void ProgerssChangedEventHandler(FileFindProgerssChangedEventArgs e);
    public delegate void FileFindCompletedEventHandler(object sender, FileFindCompletedEventArgs e);

    public class FileFinder : Component
    {
        #region Interop and costants

        [DllImport("kernel32")]
        static extern int GetDriveType(string path);

        protected const int DRIVE_CDROM = 5;
        protected const int DRIVE_FIXED = 3;
        protected const int DRIVE_NO_ROOT = 1;
        protected const int DRIVE_REMOTE = 4;
        protected const int DRIVE_REMOVABLE = 2;

        /// <summary>Enumerate shares (NT)</summary>
        [DllImport("netapi32", CharSet = CharSet.Unicode)]
        protected static extern int NetShareEnum(string lpServerName, int dwLevel,
            out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead,
            out int totalEntries, ref int hResume);

        /// <summary>Free the buffer (NT)</summary>
        [DllImport("netapi32")]
        protected static extern int NetApiBufferFree(IntPtr lpBuffer);

        /// <summary>Maximum path length</summary>
        protected const int MAX_PATH = 260;
        /// <summary>No error</summary>
        protected const int NO_ERROR = 0;
        /// <summary>Access denied</summary>
        protected const int ERROR_ACCESS_DENIED = 5;
        /// <summary>Access denied</summary>
        protected const int ERROR_WRONG_LEVEL = 124;
        /// <summary>More data available</summary>
        protected const int ERROR_MORE_DATA = 234;
        /// <summary>Not connected</summary>
        protected const int ERROR_NOT_CONNECTED = 2250;
        /// <summary>Level 1</summary>
        protected const int UNIVERSAL_NAME_INFO_LEVEL = 1;
        /// <summary>Max extries (9x)</summary>
        protected const int MAX_SI50_ENTRIES = 20;

        [Flags]
        public enum ShareType
        {
            /// <summary>Disk share</summary>
            Disk = 0,
            /// <summary>Printer share</summary>
            Printer = 1,
            /// <summary>Device share</summary>
            Device = 2,
            /// <summary>IPC share</summary>
            IPC = 3,
            /// <summary>Special share</summary>
            Special = -2147483648, // 0x80000000,
        }

        /// <summary>Share information, NT, level 1</summary>
        /// <remarks>
        /// Fallback when no admin rights.
        /// </remarks>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        protected struct SHARE_INFO_1
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string NetName;
            public ShareType ShareType;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Remark;
        }

        #endregion 

        private delegate void WorkerEventHandler(
            SearchJobConfigurationElement job, SearchLoactionConfigurationElementCollection locations, 
            SendOrPostCallback completionMethodDelegate);

        private SendOrPostCallback onProgressReportDelegate;
        private SendOrPostCallback onCompletedDelegate;
        private SendOrPostCallback completionMethodDelegate;

        private WorkerEventHandler workerDelegate;

        private HybridDictionary searchJobs = new HybridDictionary();
        private System.ComponentModel.Container components = null;
        private int _maxSearhcThreads = 15;

        /////////////////////////////////////////////////////////////
        #region Public events

        public event ProgerssChangedEventHandler ProgressChanged;
        public event FileFindCompletedEventHandler FileFindCompleted;

        #endregion

        /////////////////////////////////////////////////////////////
        #region Construction and destruction

        public FileFinder(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
            InitializeDelegates();
        }

        public FileFinder()
        {
            InitializeComponent();
            InitializeDelegates();
        }

        protected virtual void InitializeDelegates()
        {
            onProgressReportDelegate =
                new SendOrPostCallback(ReportProgress);
            onCompletedDelegate =
                new SendOrPostCallback(SearchCompleted);
            completionMethodDelegate =
                new SendOrPostCallback(CompletionMethod);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        #endregion

        /////////////////////////////////////////////////////////////
        #region Implementation

        // SearchType
        /*
        public virtual object SearchFielesAsync(SearchType type, string name, string searchCriteria)
        {
            Guid id = Guid.NewGuid();
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(id);
            
            SearchJobConfigurationElement job = new SearchJobConfigurationElement(searchCriteria);
            job.jobID = id;
            job.asyncOp = asyncOp;
            job.Stype = (byte)type;
            job.Name = name;

            // job.searchCriteria = searchCriteria;

            lock (searchJobs.SyncRoot)
            {
                searchJobs[id] = job;
            }

            workerDelegate = new WorkerEventHandler(SearchWorker);
            workerDelegate.BeginInvoke(job, new SearchLoactionConfigurationElementCollection(), completionMethodDelegate, null, null);
            return id;
        }
        */

        public virtual object SearchFilesAsync(SearchJobConfigurationElement job, SearchLoactionConfigurationElementCollection locations)
        {
            Guid id = Guid.NewGuid();
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation(id);
            job.jobID = id;
            job.asyncOp = asyncOp;

            lock (searchJobs.SyncRoot)
            {
                searchJobs[id] = job;
            }
            
            workerDelegate = new WorkerEventHandler(SearchWorker);
            workerDelegate.BeginInvoke(job, locations, completionMethodDelegate, null, null);
            return id;
        }

        public void CancelAsync(object taskId)
        {
            lock (searchJobs.SyncRoot)
            {
                object obj = searchJobs[taskId];
                if (obj != null)
                {
                    SearchJobConfigurationElement job = obj as SearchJobConfigurationElement;
                    AsyncOperation asyncOp = job.asyncOp;

                    FileFindCompletedEventArgs e = new FileFindCompletedEventArgs(
                        job.totalFiles, 
                        job.totalTime, 
                        job.modifiedFiles, 
                        job.deletedFiles, 
                        job.addedFiles,
                        job.exception,
                        true,
                        asyncOp.UserSuppliedState);
                    
                    
                    // The asyncOp object is responsible for 
                    // marshaling the call to the proper 
                    // thread or context.
                    asyncOp.PostOperationCompleted(
                        onCompletedDelegate,
                        e);
                    searchJobs.Remove(taskId);
                    job.searchResult.Clear();
                    job.searchResult = null;
                    job = null;
                }
            }
        }

        
        // This method performs the actual search files troug network or local computer.
        // It is executed on the worker thread.
        private void SearchWorker(
            SearchJobConfigurationElement job,
            SearchLoactionConfigurationElementCollection locations,
            SendOrPostCallback completionMethodDelegate)
        {
            job.timeStarted = DateTime.Now;
            FileFindProgerssChangedEventArgs e = new FileFindProgerssChangedEventArgs(
                "",
                0,
                JobStatus.JobStarted,
                0,
                job.jobID);
            ReportProgress(e);
            LogManager.LogInfo("Search job started");
            ThreadPool.SetMaxThreads(_maxSearhcThreads, _maxSearhcThreads);
            for (int i = 0; i < locations.Count; i++)
            {
                SearchOneLocationArgs args = new SearchOneLocationArgs(job, locations[i], (SearchType)job.Stype);
                Interlocked.Increment(ref job.runningThreads);
                ThreadPool.QueueUserWorkItem(new WaitCallback(SearchOneLocation), args);
            }
            while (job.runningThreads > 0)
            {
                job.are.WaitOne();
            }
            job.totalFiles = job.foundFiles.Count;
            e = new FileFindProgerssChangedEventArgs(
                    "",
                    job.totalFiles,
                    JobStatus.ScanningFinished,
                    100,
                    job.jobID);
            ReportProgress(e);

            bool noError = true;
            Musicman.Entities.Searchjob sjob = DataManager.Instance.GetSearchJob(job.Name, job.AllCriteria, (byte)job.Stype);
            if (sjob != null)
            {
                List<Searchfile> deleteList = SycnSearchedResults(ref sjob, ref job);
                if (noError)
                {
                    LogManager.LogInfo("Data from the previous job cleared");
                    noError = DataManager.Instance.DeleteListSearchFiles(deleteList);
                    noError = noError & DataManager.Instance.SaveSearchJob(sjob);
                } else 
                {
                    LogManager.LogInfo("Error deleting previous job data");
                }
            }
            else
            {
                noError = false;
            }

            job.timeFinished = DateTime.Now;

            if (!noError)
            {
                e = new FileFindProgerssChangedEventArgs(
                    "",
                    job.totalFiles,
                    JobStatus.JobFinishedWithErrors,
                    100,
                    job.jobID);
            }
            else
            {
                e = new FileFindProgerssChangedEventArgs(
                        "",
                        job.totalFiles,
                        JobStatus.ScanningFinished,
                        100,
                        job.jobID);
                ReportProgress(e);
            }

            
            job.totalTime = job.timeFinished.Ticks - job.timeStarted.Ticks;
            LogManager.LogInfo("Total time " + job.totalTime);
            if (Boolean.Parse(AppSettingsHelper.GetWebConfigValue(Musicman.Common.Constants.EmailEnabled)))
            {
                MailManager.Instance.SendNotificationMail(job);
            }
            completionMethodDelegate(job.jobID);
        }

        private void SearchOneLocation(object l)
        {
            SearchOneLocationArgs soa = l as SearchOneLocationArgs;
            Searchlocation sl = new Searchlocation();
            sl.Name = soa.Location.ServerPath;
            if (soa.SearhcType == SearchType.Local)
            {
                GetInitialFolders(sl);
            }
            else
            {
                GetNetShares(sl);
            }
            Searchfolder[] initFolders = sl.Folders.ToArray();
            for (int j = 0; j < initFolders.Length; j++)
            {
                GetAllFiles(sl, initFolders[j], soa.Job);
            }
            lock (soa.Job.searchResult)
            {
                soa.Job.searchResult.Add(sl);
            }
            Interlocked.Decrement(ref soa.Job.runningThreads);
            soa.Job.are.Set();
        }


        private List<Searchfile> SycnSearchedResults(ref Musicman.Entities.Searchjob oldResults, ref Musicman.Entities.BusinessEntities.SearchJobConfigurationElement newResults)
        {
            FileFindProgerssChangedEventArgs e = new FileFindProgerssChangedEventArgs("",
                                                                                       0,
                                                                JobStatus.ProcessingData,
                                                                                        0,
                                                                                 newResults.jobID);
            ReportProgress(e);
            LogManager.LogInfo("Processing searched data");
            oldResults.Starttime = newResults.timeStarted;
            oldResults.Endtime = DateTime.Now;
            oldResults.Totalfiles = newResults.totalFiles;
            oldResults.TotalLength = 0;
            long lastSearchSequence = DataManager.Instance.GetMaxSearchSequence((long)oldResults.Searchjobid);
 
            List<Searchfile> deleteList = new List<Searchfile>();
            List<Searchfile> removeList = new List<Searchfile>();

            for (int i = 0; i < oldResults.Files.Count; i++)
            {
                Searchfile file = oldResults.Files[i];
                if (newResults.foundFiles.Contains(file))
                {
                    Searchfile filenew = newResults.foundFiles[SearchFileCollection.GetIdentifier(file)];
                    if (Math.Abs(((DateTime)file.Changetime).Ticks - ((DateTime)filenew.Changetime).Ticks) > 99999)
                    {
                        file.CopyProperties(filenew);
                        newResults.modifiedFiles++;
                    }
                    else
                    {
                        removeList.Add(file);
                    }
                    newResults.foundFiles.Remove(file);
                    oldResults.TotalLength += file.Length;
                }
                else
                {
                    deleteList.Add(file);
                }
            }
            // remove items which are about to be deleted
            for (int i = 0; i < deleteList.Count; i++)
            {
                Searchfile file = deleteList[i];
                oldResults.Files.Remove(file);
                oldResults.Totalfiles--;
                oldResults.TotalLength -= file.Length;
                file.Folder.RemoveSearchFile(file);
                newResults.deletedFiles++;
            }
            // no need to update files that have same timestamp like in previous search
            for (int i = 0; i < removeList.Count; i++)
            {
                Searchfile file = removeList[i];
                oldResults.Files.Remove(file);
                file.Folder.RemoveSearchFile(file);
            }
            // newResults.modifiedFiles = oldResults.Files.Count;
            // the remain files in new results
            for (int i = 0; i < newResults.foundFiles.Count; i++)
            {
                Searchfile file = newResults.foundFiles[i];
                file.SearchSequence = lastSearchSequence + 1;
                oldResults.AddSearchFile(file);
                oldResults.TotalLength += file.Length;
                newResults.addedFiles++;
            }
            if (newResults.addedFiles == 0)
            {

            }
            return deleteList;
        }

        // This method is invoked via the AsyncOperation object,
        // so it is guaranteed to be executed on the correct thread.
        private void ReportProgress(object state)
        {
            FileFindProgerssChangedEventArgs e =
                state as FileFindProgerssChangedEventArgs;

            OnProgressChanged(e);
        }
        protected void OnProgressChanged(FileFindProgerssChangedEventArgs e)
        {
            if (ProgressChanged != null)
            {
                ProgressChanged(e);
            }
        }

        // This method is invoked via the AsyncOperation object,
        // so it is guaranteed to be executed on the correct thread.
        private void SearchCompleted(object operationState)
        {
            FileFindCompletedEventArgs e =
                operationState as FileFindCompletedEventArgs;

            OnFileFindCompletedCompleted(e);
        }

        protected void OnFileFindCompletedCompleted(FileFindCompletedEventArgs e)
        {
            if (FileFindCompleted != null)
            {
                FileFindCompleted(this, e);
            }
        }

        // This is the method that the underlying, free-threaded 
        // asynchronous behavior will invoke.  This will happen on
        // an arbitrary thread.
        private void CompletionMethod(object searchState)
        {
            SearchJobConfigurationElement searchJob = (SearchJobConfigurationElement)searchJobs[searchState];
            AsyncOperation asyncOp = searchJob.asyncOp;

            FileFindCompletedEventArgs e = new FileFindCompletedEventArgs(
                searchJob.totalFiles,
                searchJob.totalTime,
                searchJob.modifiedFiles,
                searchJob.deletedFiles,
                searchJob.addedFiles,
                searchJob.exception,
                false,
                asyncOp.UserSuppliedState);
            
            // In this case, don't allow cancellation, as the method 
            // is about to raise the completed event.
            lock (searchJobs.SyncRoot)
            {
                searchJobs.Remove(searchState);
            }
            searchJob.searchResult.Clear();
            searchJob.searchResult = null;
            searchJob = null;
            asyncOp.PostOperationCompleted(onCompletedDelegate, e);
        }
        #endregion

        /////////////////////////////////////////////////////////////
        #region Component Designer generated code

        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

        #endregion

        /////////////////////////////////////////////////////////////
        #region Additional working methods

        private List<String> GetDrivesList()
        {
            List<string> res = new List<string>();
            string[] drives = Directory.GetLogicalDrives();
            for (int i = 0; i < drives.Length; i++)
            {
                Console.WriteLine(drives[i]);
                if (GetDriveType(drives[i]) == DRIVE_FIXED)
                {
                    res.Add(drives[i]);
                }
            }
            return res;
        }

        private void GetInitialFolders(Searchlocation sl)
        {
            List<string> res = new List<string>();
            string[] directories = Directory.GetDirectories(sl.Name);
            for (int i = 0; i < directories.Length; i++)
            {
                DirectoryInfo di = new DirectoryInfo(directories[i]);
                Searchfolder sf = new Searchfolder(sl);
                sf.Name = di.FullName;
                sf.Changetime = di.LastWriteTime;
                sl.AddSearchfolder(sf);
            }
        }

        private void GetNetShares(Searchlocation sl)
        {
            // List<string> res = new List<string>();
            int level = 1;
            int entriesRead, totalEntries, nRet, hResume = 0;
            IntPtr pBuffer = IntPtr.Zero;

            try
            {
                nRet = NetShareEnum(sl.Name, level, out pBuffer, -1,
                out entriesRead, out totalEntries, ref hResume);
                if (nRet == NO_ERROR && entriesRead > 0)
                {
                    Type t = typeof(SHARE_INFO_1);
                    int offset = Marshal.SizeOf(t);
                    for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset)
                    {
                        IntPtr pItem = new IntPtr(lpItem);
                        SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t);
                        if (si.ShareType == ShareType.Disk)
                        {
                            try
                            {
                                DirectoryInfo di = new DirectoryInfo(sl.Name + "\\" + si.NetName);
                                Searchfolder sf = new Searchfolder();
                                sf.Name = di.FullName;
                                sf.Changetime = di.LastWriteTime;
                                // sl.Folders.Add(sf);
                                sl.AddSearchfolder(sf);
                            }
                            catch (Exception ex)
                            {
                                LogManager.LogException("Error creationg DirectoryInfo from netshare folder", ex);
                            }
                        }
                    }
                }
            }
            finally
            {
                // Clean up buffer allocated by system
                if (IntPtr.Zero != pBuffer)
                    NetApiBufferFree(pBuffer);
            }
        }

        private void GetAllFiles(Searchlocation sl, Searchfolder sf, SearchJobConfigurationElement job)
        {
            try
            {
                FileFindProgerssChangedEventArgs e = new FileFindProgerssChangedEventArgs(
                    sf.Name,
                    job.totalFiles,
                    JobStatus.Scanning,
                    0,
                    job.jobID);
                ReportProgress(e);
                bool filesFound = false;
                for (int i = 0; i < job.SearchCriteria.Length; i++)
                {
                    /* this must be changed - use direcory info insead */ 
                    string[] files = Directory.GetFiles(sf.Name, job.SearchCriteria[i]);
                    
                    if (files.Length != 0)
                    {
                        for (int j = 0; j < files.Length; j++)
                        {
                            FileInfo fi = new FileInfo(files[j]);
                            if ((job.Minsize == null || job.Minsize < fi.Length) && (job.Maxsize == null || job.Maxsize > fi.Length))
                            {
                                Searchfile sfile = new Searchfile();
                                sfile.Name = fi.Name;
                                sfile.Changetime = fi.LastWriteTime;
                                sfile.Length = fi.Length;
                                sf.AddSearchFile(sfile);
                                job.totalSize += fi.Length;
                                job.foundFiles.Add(sfile);
                            }
                        }
                        filesFound = true;
                    }
                }
                if (!filesFound)
                {
                    sl.RemoveSearchFolder(sf);
                }
                string[] directories = Directory.GetDirectories(sf.Name);
                for (int i = 0; i < directories.Length; i++)
                {
                    DirectoryInfo di = new DirectoryInfo(directories[i]);
                    Searchfolder sfolder = new Searchfolder();
                    sfolder.Name = di.FullName;
                    sfolder.Changetime = di.LastWriteTime;
                    sl.AddSearchfolder(sfolder);
                    GetAllFiles(sl, sfolder, job);
                }
            }
            catch (Exception ex)
            {
                LogManager.LogException("Directory can't be read due to the following reason: ", ex);
                lock (sl)
                {
                    sl.Folders.Remove(sf);
                }
            }
        }
        #endregion

    }

    #region Arguments and helper classes
    public class FileFindProgerssChangedEventArgs : ProgressChangedEventArgs
    {
        private string currentDir;
        private long totalFiles;
        private JobStatus status;

        public FileFindProgerssChangedEventArgs(string currentDir, long totalFiles, JobStatus status, int percentage, object userToken)
            : base(percentage, userToken)
        {
            this.currentDir = currentDir;
            this.totalFiles = totalFiles;
            this.status = status;
        }

        public string CurrentDir
        {
            get { return currentDir; }
            set { currentDir = value; }
        }

        public long TotalFiles
        {
            get { return totalFiles; }
            set { totalFiles = value; }
        }

        public JobStatus Status
        {
            get { return status; }
            set { status = value; }
        }
    }

    public class FileFindCompletedEventArgs : AsyncCompletedEventArgs
    {
        private long totalFiles;
        private long totalFileSize;
        private long totalTime;
        private long totalDeleted;
        private long totalModified;
        private long totalAdded;

        public FileFindCompletedEventArgs(
            long totalfiles, long totaltime, long totalmodified, long totaldeleted, long totaladded,
            Exception e, bool cancelled, object state)
            : base(e, cancelled, state)
        {
            this.totalFiles = totalfiles;
            this.totalTime = totaltime;
            this.totalModified = totalmodified;
            this.totalDeleted = totaldeleted;
            this.totalAdded = totaladded;
        }

        public long TotalFiles
        {
            get { return totalFiles; }
            set { totalFiles = value; }
        }
        public long TotalFileSize
        {
            get { return totalFileSize; }
            set { totalFileSize = value; }
        }
        public string TotalTime
        {
            get
            {
                TimeSpan tSpan = new TimeSpan(totalTime);
                return String.Format("{0}:{1}:{2}.{3}", tSpan.Hours, tSpan.Minutes, tSpan.Seconds, tSpan.Milliseconds);
            }
        }
        public long TotalDeleted
        {
            get { return totalDeleted; }
            set { totalDeleted = value; }
        }
        public long TotalModified
        {
            get { return totalModified; }
            set { totalModified = value; }
        }
        public long TotalAdded
        {
            get { return totalAdded; }
            set { totalAdded = value; }
        }
    }

    public enum JobStatus : byte
    {
        JobStarted,
        DataRead,
        Scanning,
        ScanningFinished,
        ProcessingData,
        SavingResults,
        JobFinished,
        JobFinishedWithErrors
    }

    internal class SearchOneLocationArgs
    {
        private SearchJobConfigurationElement _job;
        private SearchLoacationConfigurationElement _location;
        private SearchType _searhcType;

        public SearchOneLocationArgs(SearchJobConfigurationElement job, SearchLoacationConfigurationElement location, SearchType searchtype)
        {
            this._job = job;
            this._location = location;
            this._searhcType = searchtype;
        }

        public SearchJobConfigurationElement Job
        {
            get { return _job; }
        }

        public SearchLoacationConfigurationElement Location
        {
            get { return _location; }
        }

        public SearchType SearhcType
        {
            get { return _searhcType; }
        }
    }
    #endregion 

}
